package com.mccken.eshop.common.core;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
 * 基础pojo类
 *
 * @program: mccken-collect
 * @description: 基础pojo类
 * @author: mccken
 * @create: 2022-08-27 23:04
 **/
public abstract class AbstractObject {

	/**
	 * 浅度克隆
	 *
	 * @param targetClazz 目标对象额Class类型
	 * @param <T>
	 * @return 目标对象实例
	 */
	public <T> T clone(Class<T> targetClazz) {
		try {
			T target = targetClazz.newInstance();
			BeanCopierUtil.copyProperties(this, target);
			return this.getTarget(target);
		} catch (Exception e) {
			throw new RuntimeException("error", e);
		}
	}

	/**
	 * 浅度克隆
	 *
	 * @param target
	 * @param <T>
	 * @return
	 */
	public <T> T clone(T target) {
		try {
			BeanCopierUtil.copyProperties(this, target);
			return this.getTarget(target);
		} catch (Exception e) {
			throw new RuntimeException("error", e);
		}
	}

	/**
	 * 深度克隆 <p>
	 * 两个对象中的属性名，需要完全一样 <p>    
	 * 只能克隆 AbstractObject 对象, 因为这就是 AbstractObject 中的克隆功能
	 *
	 * @param targetClazz         目标对象的class类型
	 * @param cloneDirectionEnums 深度克隆的方向
	 * @param <T>
	 * @return 目标对象实例
	 * @author: 骆文军
	 */
	public <T> T clone(Class<T> targetClazz, CloneDirectionEnums cloneDirectionEnums) {

		try {
			// 先完成基本字段的浅克隆
			T target = targetClazz.newInstance();
			BeanCopierUtil.copyProperties(this, target);

			// 完成内部的 AbstractObject 、 List<Abstract> 类型字段的深度克隆
			Class<? extends AbstractObject> thisClazz = this.getClass();
			// 递归获取当前类及父类中的对象
			List<Field> fieldList = this.listThisField(null, thisClazz);

			for (Field field : fieldList) {
				field.setAccessible(true);

				Class<?> sourceFieldClazz = field.getType();

				// 判断某个字段是否为List类型
				if (!Collection.class.isAssignableFrom(sourceFieldClazz)) { // 不是List类型

					// 基本字段，直接跳过
					if (sourceFieldClazz == String.class
							|| sourceFieldClazz == BigDecimal.class
							|| sourceFieldClazz == Integer.class || "int".equals(sourceFieldClazz.toString())
							|| sourceFieldClazz == Long.class || "long".equals(sourceFieldClazz.toString())
							|| sourceFieldClazz == Short.class || "short".equals(sourceFieldClazz.toString())
							|| sourceFieldClazz == Double.class || "double".equals(sourceFieldClazz.toString())
							|| sourceFieldClazz == Float.class || "float".equals(sourceFieldClazz.toString())
							|| sourceFieldClazz == Character.class || "char".equals(sourceFieldClazz.toString())
							|| sourceFieldClazz == Byte.class || "byte".equals(sourceFieldClazz.toString())
							|| sourceFieldClazz == Boolean.class || "boolean".equals(sourceFieldClazz.toString())
							|| sourceFieldClazz == Date.class
							|| sourceFieldClazz == java.sql.Date.class
							|| sourceFieldClazz == LocalDate.class
							|| sourceFieldClazz == LocalDateTime.class) {
						continue;
					}

					try {
						if (!(sourceFieldClazz.newInstance() instanceof AbstractObject)) {
							continue;
						}
					} catch (Exception e) {
						if (e instanceof InstantiationException) {
							continue;
						}
						throw new RuntimeException("error", e);
					}

					AbstractObject sourceObj = (AbstractObject) field.get(this);
					if (sourceObj == null) {
						continue;
					}

					// 获取要克隆到的目标类型
					Field targetField = null;

					try {
						targetField = this.getTargetClazzField(field, targetClazz);
					} catch (NoSuchFieldException e) {
						continue;
					}

					if (targetField != null) {
						Class<?> targetFieldType = targetField.getType();
						AbstractObject cloneObj = (AbstractObject) sourceObj.clone(targetClazz, cloneDirectionEnums);

						// 获取设置克隆号到的对象的方法名称
						String name = field.getName();
						String setMethodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
						Method setFieldMethod = targetClazz.getMethod(setMethodName, targetFieldType);
						setFieldMethod.invoke(cloneObj, targetField);
					}
				} else { // 某个字段是List 类型的
					Collection<?> list = (Collection<?>) field.get(this);
					if (list == null && list.size() == 0) {
						continue;
					}

					// 获取List集合中的泛型类型
					Field targetFiled = null;
					try {
						targetFiled = this.getTargetClazzField(field, targetClazz);
					} catch (NoSuchFieldException e) {
						continue;
					}
					
					if (targetFiled != null) {
						// 真正获取List集合中的泛型类型
						Class<?> cloneTargetClazz = this.getTargetListGenericType(targetFiled);
						// 将list集合克隆到目标list集合中
						Collection cloneList = (Collection) field.get(this).getClass().newInstance();
						this.cloneList(list, cloneList, cloneTargetClazz, cloneDirectionEnums);
						
						String name = field.getName();
						String setMethodName = "set" + name.substring(0,1).toUpperCase() + name.substring(1);
						Method setFieldMethod = targetClazz.getMethod(setMethodName, field.getType());
						setFieldMethod.invoke(target, cloneList);
					}

				}

			}

			return target;
		} catch (Exception e) {
			throw new RuntimeException("error", e);
		}

	}

	/**
	 * 将一个List克隆到另一个List <p>
	 * 如果继承了 AbstractObject 的对象，就是创建新的对象，如果没有继承AbstractObject，那么是直接拿原对象    
	 * 
	 * @param sourceList
	 * @param targetList
	 * @param cloneTargetClazz
	 * @param cloneDirectionEnums
	 */
	private void cloneList(Collection sourceList, Collection targetList, Class<?> cloneTargetClazz, CloneDirectionEnums cloneDirectionEnums) {

		for (Object object : sourceList) {
			if (object instanceof AbstractObject) {
				AbstractObject targetObject = (AbstractObject) object;
				AbstractObject clonedObject = (AbstractObject) targetObject.clone(cloneTargetClazz, cloneDirectionEnums);
				targetList.add(clonedObject);
			} else {
				// 非List<? extends AbstractObject> 类型的集合字段，直接复用原对象的字段值 	
				targetList.add(object);	
			}
		} 
		
	}

	/**
	 * 如果目标有继承父类需要递归获取目标字段
	 *
	 * @param field
	 * @param targetClazz
	 * @param <T>
	 * @return
	 * @throws NoSuchFieldError
	 */
	private <T> Field getTargetClazzField(Field field, Class<T> targetClazz) throws NoSuchFieldException {
		Field targetField = null;
		try {
			targetField = targetClazz.getDeclaredField(field.getName());
		} catch (NoSuchFieldException e) {

			// 目标类有可能没有继承AbstractObject类
			if (targetClazz.getSuperclass() != null) {
				String targetSupperClazzTypeName = targetClazz.getSuperclass().getTypeName();
				if (!targetSupperClazzTypeName.equals(Object.class.getTypeName()) && !targetSupperClazzTypeName.equals(AbstractObject.class.getTypeName())) {
					// 递归
					targetField = this.getTargetClazzField(field, targetClazz.getSuperclass());
				}
			}

			if (targetField == null) {
				throw e;
			}
		}

		return targetField;
	}

	/**
	 * 递归获取当前类以及父类中的字段
	 */
	private List<Field> listThisField(List<Field> thisFields, Class<?> thisClazz) {
		if (thisFields == null) {
			thisFields = new ArrayList<>(Arrays.asList(thisClazz.getDeclaredFields()));
		} else {
			thisFields.addAll(Arrays.asList(thisClazz.getDeclaredFields()));
		}

		if (!thisClazz.getSuperclass().getTypeName().equals(AbstractObject.class.getTypeName())) {
			this.listThisField(thisFields, thisClazz.getSuperclass());
		}

		return thisFields;
	}

	/**
	 * 浅度克隆时，原对象list属性的处理
	 *
	 * @param target
	 * @param <T>
	 * @return
	 */
	private <T> T getTarget(T target) throws Exception {
		Class<?> targetClazz = target.getClass();
		Field[] fields = targetClazz.getDeclaredFields();

		for (Field field : fields) {
			field.setAccessible(true);

			// 如果判断某个字段是List类型
			if (field.getType() != List.class) {
				continue;
			}

			List<?> list = (List<?>) field.get(target);
			if (list == null || list.size() == 0) {
				continue;
			}

			Class<?> targetListGenericTypeClazz = this.getTargetListGenericType(field);
			if (targetListGenericTypeClazz != null) {
				boolean isAbstractObject = isAbstractObjectClass(targetListGenericTypeClazz);
				if (!isAbstractObject) {
					continue;
				}
			}

			String name = field.getName();
			String setMethodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);

			Method setFieldMethod = targetClazz.getMethod(setMethodName, field.getType());
			setFieldMethod.invoke(target, new ArrayList<>());
		}

		return target;
	}

	/**
	 * 获取List集合的泛型类型
	 * 
	 * @param targetField
	 * @return
	 */
	private Class<?> getTargetListGenericType(Field targetField) {
		Type genericType = targetField.getGenericType();
		if (genericType instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType) genericType;
			return (Class<?>) parameterizedType.getActualTypeArguments()[0];
		}
		return null;
	}

	private boolean isAbstractObjectClass(Class clazz) {
		// 目标类有可能没有继承AbstractObject类
		if (clazz.getSuperclass() != null) {
			String superClazzTypeName = clazz.getSuperclass().getTypeName();

			if (superClazzTypeName.equals(Object.class.getTypeName())) {
				return false;
			}

			if (superClazzTypeName.equals(AbstractObject.class.getTypeName())) {
				return true;
			} else {
				this.isAbstractObjectClass(clazz.getSuperclass());
			}
		}

		return false;
	}

}
